/*
*    Phantom Test Driver v2.0 GUI Automation Software
*    Copyright (C) 2009  John C. Van Arsdall (john@phantomtest.com)
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License along
*    with this program; if not, write to the Free Software Foundation, Inc.,
*    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
/*
 * LineNumberView.java
 *
 * Created on March 15, 2006, 8:56 PM
 *
 * To change this template, choose Tools | Options and locate the template under
 * the Source Creation and Management node. Right-click the template and choose
 * Open. You can then make changes to the template in the Source Editor.
 */

// Based on code by Alan Moore, but I can't find the original code or copyright.  If
// anyone has it, feel free to add it.  However, this code was modified by me.

package ssi_linenumberview;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
 
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
 
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.SizeSequence;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;

import javax.swing.ImageIcon;
import java.awt.Image;
 
public class LineNumberView extends JComponent
{
  // This is for the border to the right of the line numbers.
  // There's probably a UIDefaults value that could be used for this.
  private static final Color BORDER_COLOR = Color.GRAY;
 
  private static final int WIDTH_TEMPLATE = 99999;
  private static final int MARGIN = 5;
 
  private FontMetrics viewFontMetrics;
  private int maxNumberWidth;
  private int componentWidth;
 
  public int textTopInset;
  public int textFontAscent;
  public int textFontHeight;
 
  private JTextComponent text;
  private SizeSequence sizes;
  public int startLine = 0;
  public int firstVisibleLine = 0;
  public int lastVisibleLine = 0;
  private boolean structureChanged = true;

  public LineIconManager IconMgr;
    
  public LineNumberView(JTextComponent text)
  {
    
    if (text == null)
    {
      throw new IllegalArgumentException("Textarea cannot be null");
    }
    
    IconMgr = new LineIconManager();
    
    this.text = text;
    updateCachedMetrics();
 
    UpdateHandler handler = new UpdateHandler();
    text.getDocument().addDocumentListener(handler);
    text.addPropertyChangeListener(handler);
    text.addComponentListener(handler);
    //this.addMouseListener(new LineNumberMouseListener());
 
    setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, BORDER_COLOR));
  }
 
  private void viewChanged(int startLine, boolean structureChanged)
  {
    this.startLine = startLine;
    this.structureChanged = structureChanged;
 
    revalidate();
    repaint();
  }

  // Redraws the line number view
  public void update()
  {
    revalidate();
    repaint();
  }

  private void updateSizes()
  {
    if (startLine < 0)
    {
      return;
    }
 
    if (structureChanged)
    {
      int count = getAdjustedLineCount();
      sizes = new SizeSequence(count);
      for (int i = 0; i < count; i++)
      {
        sizes.setSize(i, getLineHeight(i));
      }
      structureChanged = false;
    }
    else
    {
      sizes.setSize(startLine, getLineHeight(startLine));
    }
 
    startLine = -1;
  }
 
  public int getAdjustedLineCount()
  {
    Element map = text.getDocument().getDefaultRootElement();
    int n = map.getElementCount();
    Element lastLine = map.getElement(n - 1);
    if ((lastLine.getEndOffset() - lastLine.getStartOffset()) > 1)
    {
      return n;
    }
 
    return n - 1;
  }
 
  public int getLineHeight(int index)
  {
      
      // This area causes a JVM crash with function file openning in the
      // debugger, so by trial and error return 17
      // KEEP THE COMMENTED CODE IN CASE NEEDED LATER... I DIDNT WRITE THIS PART, I DONT THINK...
      return 17;
      /*
    Main.debug_out("LineH: " + index);
    int lastPos = sizes.getPosition(index) + textTopInset;
    //return 15;
    
    int height = textFontHeight;
    try
    {
        
      Main.debug_out("LineH 1: " + index);
      Element map = text.getDocument().getDefaultRootElement();
      int n = map.getElementCount();
      Main.debug_out("LineH 2: " + index);
      Main.debug_out("LineH 2b: " + n);
      if(index >= n){
          Main.debug_out("LineH ret1: " + n);
          return height;
      }
      
      Main.debug_out("LineH 3: " + index);
      int lastChar = map.getElement(index).getEndOffset() - 1;
      Main.debug_out("LineH 3b: " + index);
      Rectangle r = text.modelToView(lastChar);
      height = (r.y - lastPos) + r.height;
      Main.debug_out("LineH 3c: " + index);
         
    }
    //catch (BadLocationException ex)
    catch (Exception ex)
    {
      Main.debug_out("LineH 4: " + index);
      ex.printStackTrace();
    }
    Main.debug_out("LineH end: " + index);
    return height;
       
       
      //return 10;
       * */
  }
 
  private void updateCachedMetrics()
  {
    Font textFont = text.getFont();
    FontMetrics fm = getFontMetrics(textFont);
    textFontHeight = fm.getHeight();
    textFontAscent = fm.getAscent();
    textTopInset = text.getInsets().top;
 
    Font viewFont = getFont();
    boolean changed = false;
    if (viewFont == null)
    {
      viewFont = UIManager.getFont("Label.font");
      changed = true;
    }
    if (viewFont.getSize() > textFont.getSize())
    {
      viewFont = viewFont.deriveFont(textFont.getSize2D());
      changed = true;
    }
 
    viewFontMetrics = getFontMetrics(viewFont);
    maxNumberWidth = viewFontMetrics.stringWidth(String.valueOf(WIDTH_TEMPLATE));
    componentWidth = 2 * MARGIN + maxNumberWidth;
 
    if (changed)
    {
      super.setFont(viewFont);
    }
  }
 
  public Dimension getPreferredSize()
  {
    return new Dimension(componentWidth, text.getHeight());
  }
 
  public void setFont(Font font)
  {
    super.setFont(font);
    updateCachedMetrics();
  }
 
  public void paintComponent(Graphics g)
  {
    updateSizes();
    Rectangle clip = g.getClipBounds();
 
    g.setColor(getBackground());
    g.fillRect(clip.x, clip.y, clip.width, clip.height);
 
    g.setColor(getForeground());
    int base = clip.y - textTopInset;
    int first = sizes.getIndex(base);
    int last = sizes.getIndex(base + clip.height);
    
    firstVisibleLine = first;
    lastVisibleLine = last;
    
    String text = "";
    
    for (int i = first; i <= last; i++)
    {
      text = String.valueOf(i+1);
      int x = MARGIN + maxNumberWidth - viewFontMetrics.stringWidth(text);
      int y = sizes.getPosition(i) + textFontAscent + textTopInset;
      
      int ind = 0;
      Image Img;

      int img_y = y - textFontAscent; //- textFontHeight;
      int img_x = 2;
      
      while((Img = IconMgr.getImage(i+1, ind)) != null){
          g.drawImage(Img, img_x, img_y, null);
          ind = ind + 1;
      }
      
      g.drawString(text, x, y);
    }
  }
 
 
  class UpdateHandler extends ComponentAdapter
      implements PropertyChangeListener, DocumentListener
  {
    public void componentResized(ComponentEvent evt)
    {
      viewChanged(0, true);
    }
 
    public void propertyChange(PropertyChangeEvent evt)
    {
      Object oldValue = evt.getOldValue();
      Object newValue = evt.getNewValue();
      String propertyName = evt.getPropertyName();
      if ("document".equals(propertyName))
      {
        if (oldValue != null && oldValue instanceof Document)
        {
          ((Document)oldValue).removeDocumentListener(this);
        }
        if (newValue != null && newValue instanceof Document)
        {
          ((Document)newValue).addDocumentListener(this);
        }
      }
 
      updateCachedMetrics();
      viewChanged(0, true);
    }
 
    public void insertUpdate(DocumentEvent evt)
    {
      update(evt);
    }
  
    public void removeUpdate(DocumentEvent evt)
    {
      update(evt);
    }
  
    public void changedUpdate(DocumentEvent evt)
    {
    }
  
    private void update(DocumentEvent evt)
    {
      Element map = text.getDocument().getDefaultRootElement();
      int line = map.getElementIndex(evt.getOffset());
      DocumentEvent.ElementChange ec = evt.getChange(map);
      viewChanged(line, ec != null);
    }
  }
}

